package org.akomantoso.core.io.impl;

import java.io.IOException;
import java.util.Map;

import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;

import org.akomantoso.core.data.AkomaNtoso;
import org.akomantoso.core.data.AnyOtherType;
import org.akomantoso.core.data.AnyOtherTypeElement;
import org.akomantoso.core.data.other.ExternalTag;
import org.akomantoso.core.io.Input;
import org.akomantoso.core.io.InputConstants;
import org.akomantoso.core.io.InputFactory;
import org.akomantoso.core.io.util.Instantiator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

final class StaxXmlInput implements Input {

	public static final String NAMESPACE_URI_SCHEMA_INSTANCE = "http://www.w3.org/2001/XMLSchema-instance";
	
	public static final String NAMESPACE_URI_AKOMA_NTOSO_V2 = "http://www.akomantoso.org/2.0";
	
	private static final Logger LOGGER = LoggerFactory.getLogger(StaxXmlInput.class);
	
	static final XMLInputFactory XML_INPUT_FACTORY = XMLInputFactory.newInstance();

	static {
		XML_INPUT_FACTORY.setProperty(XMLInputFactory.IS_COALESCING, Boolean.TRUE);
		XML_INPUT_FACTORY.setProperty(XMLInputFactory.IS_REPLACING_ENTITY_REFERENCES, Boolean.TRUE);
		XML_INPUT_FACTORY.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, Boolean.TRUE);
		XML_INPUT_FACTORY.setProperty(XMLInputFactory.SUPPORT_DTD, Boolean.FALSE);
		XML_INPUT_FACTORY.setProperty(XMLInputFactory.IS_VALIDATING, Boolean.FALSE);
	}

	private final XMLStreamReader _reader;
	private final InputFactory _inputFactory;

	// to refactor
	private final boolean _backwardCompatible;
	private final Map<Class<?>, BackwardCompatible> _map;

	StaxXmlInput(InputFactory inputFactory, XMLStreamReader reader) {
		_inputFactory = inputFactory;
		_reader = reader;
		//_namespaces = new HashMap<String, String>();
	
//		String prefix,uri;
		
//		for (int i = 0, n = reader.getNamespaceCount(); i < n; i++) {
//			
//			if ((uri=reader.getNamespaceURI(i)).startsWith("http://www.akomantoso.org")) {
//				continue;
//			}
//			if ("http://www.w3.org/2001/XMLSchema-instance".equals(uri)) {
//				continue;
//			}
//			prefix = reader.getNamespacePrefix(i);
//			
//			if (LOGGER.isDebugEnabled()) {
//				LOGGER.debug("Support namespace : prefix=[{}] - uri=[{}]", prefix,uri);
//			}
//			//_namespaces.put(prefix, uri);
//		}

		_backwardCompatible = false;
		_map = null;
	}

	StaxXmlInput(InputFactory inputFactory, XMLStreamReader reader, boolean backwardCompatible,
			Map<Class<?>, BackwardCompatible> map) {
		_inputFactory = inputFactory;
		_reader = reader;
		_backwardCompatible = backwardCompatible;
		_map = map;
		//_namespaces = new HashMap<String, String>();
	}

	/**
	 * @see ec.ep.akomantoso.io.Input#getEventType()
	 */
	public int getEventType() {
		return _reader.getEventType();
	}

	/**
	 * @see ec.ep.akomantoso.io.Input#getLocalName()
	 */
	public String getLocalName() {
		return _reader.getLocalName();
	}

	
	/**
	 * @see org.akomantoso.core.io.Input#readNamespace(org.akomantoso.core.data.AkomaNtoso)
	 */
	@Override
	public void readNamespace(AkomaNtoso akomaNtoso) throws IOException {
		String prefix;
		for (int i = 0, n = _reader.getNamespaceCount() ; i < n ; i++) {
			prefix = _reader.getNamespaceURI(i);
			if (NAMESPACE_URI_SCHEMA_INSTANCE == prefix  || NAMESPACE_URI_AKOMA_NTOSO_V2 == prefix) {
				continue;
			}
			akomaNtoso.addNamespace(_reader.getNamespacePrefix(i),_reader.getNamespaceURI(i));
		}
	}
	
	

	/**
	 * @see ec.ep.akomantoso.io.Input#readNextStartElement()
	 */
	public void readNextStartElement() throws IOException {
		int eventType;
		do {
			try {
				eventType = _reader.next();
			} catch (XMLStreamException cause) {
				throw new IOException("Failed to read next tag ?!? [" + cause.getMessage() + "]", cause);
			}
		} while (eventType != XMLStreamConstants.START_ELEMENT && eventType != XMLStreamConstants.END_DOCUMENT);
	}

	/**
	 * @see org.akomantoso.core.io.Input#readNextStartOrEndElement()
	 */
	@Override
	public void readNextStartOrEndElement() throws IOException {
		int eventType;
		do {
			try {
				eventType = _reader.next();
			} catch (XMLStreamException cause) {
				throw new IOException("Failed to read next tag ?!? [" + cause.getMessage() + "]", cause);
			}
		} while (eventType != XMLStreamConstants.START_ELEMENT && eventType != XMLStreamConstants.END_DOCUMENT
				&& eventType != XMLStreamConstants.END_ELEMENT);
	}

	public int readNext() throws IOException {

		try {
			return _reader.next();
		} catch (XMLStreamException cause) {
			throw new IOException("Failed to read next tag ?!?", cause);
		}

	}

	/**
	 * @see ec.ep.akomantoso.io.Input#readContent()
	 */
	public String readContent() throws IOException {
		return _reader.getText();
	}

	/**
	 * @see org.akomantoso.core.io.Input#readExternal(org.akomantoso.core.data.AnyOtherType)
	 */
	@Override
	public void readExternal(AnyOtherType anyOtherType) throws IOException {

		final String localName = _reader.getLocalName();

		if (_reader.getNamespaceCount() > 0) {
			for (int i = 0 ; i < _reader.getNamespaceCount() ; i++) {
				anyOtherType.addNamespace(_reader.getNamespacePrefix(i),_reader.getNamespaceURI(i));
			}	
		}
		
		
		
		int eventType;
		Instantiator<AnyOtherTypeElement> instantiator;
		Map<String, Instantiator<AnyOtherTypeElement>> map;
		AnyOtherTypeElement element;
		
		while (true) {
			
			try {
				eventType = _reader.next();
			} catch (XMLStreamException cause) {
				throw new IOException("Failed to read next tag ?!?", cause);
			}
				
			switch (eventType) {
				case InputConstants.START_ELEMENT: {
					map = _inputFactory.getMap(_reader.getNamespaceURI());
					if (map == null) {
						LOGGER.warn("No management of namespace [{}] -> tag [{}] -> Create ExternalTag ",_reader.getNamespaceURI(), _reader.getLocalName());
						
						ExternalTag externalTag = new ExternalTag(_reader.getPrefix(), _reader.getLocalName());
						anyOtherType.addOtherElement(externalTag);
						for (int i = 0 , n = _reader.getAttributeCount() ; i < n ; i++) {
							externalTag.add(_reader.getAttributeLocalName(i),_reader.getAttributeValue(i));
						}
						
						
						continue;
					} else {
						instantiator = map.get(_reader.getLocalName());;
						if (instantiator == null) {
							LOGGER.warn("Tag [{}] not supported or not defined for namespace [{}] -> skip ...", _reader.getLocalName(),_reader.getNamespaceURI());
							continue;
						}
						
						anyOtherType.addOtherElement(element = instantiator.instantiate());
						element.readExternal(this);
					}
					
				}
				case InputConstants.END_ELEMENT:
					if (localName.equals(_reader.getLocalName())) {
						return;
					}
			}
			
		}
		

	}

	/**
	 * @see org.akomantoso.core.io.Input#isNotLastVersion()
	 */
	@Override
	public boolean isNotLastVersion() {
		return _backwardCompatible;
	}

	/**
	 * @see org.akomantoso.core.io.Input#doBackwardCompatible(java.lang.Class, java.lang.Object)
	 */
	@Override
	public void doBackwardCompatible(Class<?> clazz, Object object) throws IOException {

		BackwardCompatible backwardCompatible = _map.get(clazz);

		if (backwardCompatible != null) {
			backwardCompatible.execute(this, object);
		}

	}

	/**
	 * @see ec.ep.akomantoso.io.Input#readAttribute(java.lang.String)
	 */
	public String readAttribute(String name) throws IOException {
		return _reader.getAttributeValue(null, name);
	}

	/**
	 * @see org.akomantoso.core.io.Input#readAttribute(java.lang.String, java.lang.String)
	 */
	@Override
	public String readAttribute(String prefix, String name) throws IOException {
		return _reader.getAttributeValue(prefix, name);
	}

	/**
	 * @return 
	 * @see org.akomantoso.core.io.Input#isWhiteSpace()
	 */
	@Override
	public boolean isWhiteSpace() {
		return _reader.isWhiteSpace();
	}
	
	

}